package com.my.java8;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class CollectorsTest {

	
	public static boolean isPrime(int candidate) {
		int candidateRoot = (int) Math.sqrt((double) candidate);
		return IntStream.rangeClosed(2, candidateRoot).noneMatch(i -> candidate % i == 0);
	}
	
	public static class ToListCollector<T> implements Collector<T, List<T>, List<T>>{

		@Override
		public BiConsumer<List<T>, T> accumulator() {
			return List::add;
		}

		@Override
		public Set<java.util.stream.Collector.Characteristics> characteristics() {
			
			return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH,Characteristics.CONCURRENT));
		}

		@Override
		public BinaryOperator<List<T>> combiner() {
			
			return (l1,l2)->{l1.addAll(l2);return l1;};
		}

		@Override
		public Function<List<T>, List<T>> finisher() {
			
			return Function.identity();
		}

		@Override
		public Supplier<List<T>> supplier() {
			
			return ArrayList::new;
		}
		
	}
	
	public static void main(String[] args) {
		List<Dish> menu = Arrays.asList(new Dish("pork", false, 800, Dish.Type.MEAT),
				new Dish("beef", false, 700, Dish.Type.MEAT), new Dish("chicken", false, 400, Dish.Type.MEAT),
				new Dish("french fries", true, 530, Dish.Type.OTHER), new Dish("rice", true, 350, Dish.Type.OTHER),
				new Dish("season fruit", true, 120, Dish.Type.OTHER), new Dish("pizza", true, 550, Dish.Type.OTHER),
				new Dish("prawns", false, 300, Dish.Type.FISH), new Dish("salmon", false, 450, Dish.Type.FISH));
		
		Comparator<Dish> dishCaloriesComparator =
				Comparator.comparingInt(Dish::getCalories);
		Optional<Dish> mostCalorieDish =
				menu.stream()
				.collect(Collectors.maxBy(dishCaloriesComparator));
		System.out.println(mostCalorieDish);
		
		int totalCalories = menu.stream().collect(Collectors.summingInt(Dish::getCalories));
		System.out.println(totalCalories);

		IntSummaryStatistics menuStatistics =
				menu.stream().collect(Collectors.summarizingInt(Dish::getCalories));
		System.out.println(menuStatistics);
		
		int totalCalories2 = menu.stream().collect(Collectors.reducing(
				0, Dish::getCalories, Integer::sum));
		System.out.println(totalCalories2);
		
		
		/*Stream<Integer> stream = Arrays.asList(1, 2, 3, 4, 5, 6).stream();
		List<Integer> numbers = stream.reduce(new ArrayList<Integer>(),
				(List<Integer> l, Integer e) -> {
					l.add(e);
					return l; 
				},
				(List<Integer> l1, List<Integer> l2) -> {
					l1.addAll(l2);
					return l1; 
				}
		);
		System.out.println(numbers);*/
		
		int totalCalories3 =
				menu.stream().map(Dish::getCalories).reduce(Integer::sum).get();
		
		int totalCalories4 = menu.stream().mapToInt(Dish::getCalories).sum();
		
		Map<Dish.Type, List<Dish>> dishesByType = menu.stream().collect(Collectors.groupingBy(Dish::getType));
		
		menu.stream().collect(Collectors.groupingBy(dish->{
			if (dish.getCalories() <= 400) return CaloricLevel.DIET;
			else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
			else return CaloricLevel.FAT;
		})).forEach((t,l)->{System.out.println(t+" "+l);});
		
		Map<Dish.Type, Long> typesCount = menu.stream().collect(Collectors.groupingBy(Dish::getType,Collectors.counting()));
		System.out.println(typesCount);
		
		Map<Dish.Type, Optional<Dish>> mostCaloricByType =
				menu.stream().collect(Collectors.groupingBy(Dish::getType,
						Collectors.maxBy(Comparator.comparingInt(Dish::getCalories))));
		
		
		Map<Dish.Type, Dish> mostCaloricByType2 =
				menu.stream().collect(Collectors.groupingBy(Dish::getType,Collectors.collectingAndThen(
						Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)), Optional::get)));
		
		Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType =
				menu.stream().collect(
						Collectors.groupingBy(Dish::getType, Collectors.mapping(dish -> { 
							if (dish.getCalories() <= 400) return CaloricLevel.DIET;
							else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
							else return CaloricLevel.FAT; 
						},
				Collectors.toSet() )));
		
		Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType2 =
				menu.stream().collect(
						Collectors.groupingBy(Dish::getType, Collectors.mapping(
				dish -> { if (dish.getCalories() <= 400) return CaloricLevel.DIET;
				else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
				else return CaloricLevel.FAT; },
				Collectors.toCollection(HashSet::new) )));
		
		Map<Boolean, Dish> mostCaloricPartitionedByVegetarian =
				menu.stream().collect(
						Collectors.partitioningBy(Dish::isVegetarian,
								Collectors.collectingAndThen(
										Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)),
				Optional::get)));
		
		Map<Boolean, List<Integer>> primes = IntStream.rangeClosed(2, 100).boxed()
				.collect(Collectors.partitioningBy(candidate -> isPrime(candidate)));
	
	
		System.out.println(primes.get(true));
		
		List<Dish> dishes = menu.stream().collect(new ToListCollector<Dish>());
	
	}
	
	public enum CaloricLevel { DIET, NORMAL, FAT }

}
